package com.aljfce.business.calendar.control;

import com.aljfce.business.entity.Calendar;
import com.aljfce.business.entity.CalendarPK;
import com.aljfce.business.entity.Event;
import com.aljfce.business.entity.User;
import com.aljfce.business.entity.WeatherInformation;
import com.aljfce.business.forecast.control.ForecastManager;
import com.aljfce.business.forecast.exception.DayOutOfRangeException;
import com.aljfce.business.forecast.exception.ForecastNotFoundException;
import com.aljfce.business.profile.control.ProfileManager;
import com.aljfce.business.profile.exception.UserNotFoundException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.http.HttpServletRequest;
import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Clazz;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.DtEnd;
import net.fortuna.ical4j.model.property.DtStart;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.LocationType;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Summary;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;
import org.primefaces.model.UploadedFile;

/**
 *
 * @author EOroan, Antonio
 */
@Named
@Stateless
public class CalendarManager {

    @PersistenceContext
    EntityManager em;

    @Inject
    ProfileManager pm;

    @Inject
    ForecastManager fm;

    private Calendar calendar;

    //TODO ANTONIO used?
    /**
     * Returns the value of calendar
     *
     * @return the value of calendar
     */
    public Calendar getCalendar() {

        return calendar;
    }

    public void init() {
        this.calendar = new Calendar();
    }

    /**
     * This method given an email address returns the "default" calendar
     * associated to the email in the persistence context
     *
     * @param email A {@link String} representing with the {@link Calendar}'s
     * name the {@link Calendar}'s primary key.
     * @return The {@link Calendar} associated to the email and named "default"
     */
    public Calendar getCalendar(String email) {

        CalendarPK cPK = new CalendarPK(email);

        return em.find(Calendar.class, cPK);

    }

    /**
     * Set the value of calendar
     *
     * @param calendar new value of calendar
     */
    public void setCalendar(Calendar calendar) {
        this.calendar = calendar;
    }

    //FIXME ANTONIO
    /**
     *
     * @param event To add to a {@link calendar}. Used to add an event accepted
     * by the {@link User} identified by the <i>email</i> in parameter
     * @param email Of the user associated to the calendar where the event is
     * added.
     */
    public void addEvent(Event event, String email) {
        Calendar current = this.getCalendar(email);
        current.addEvent(event);
        em.merge(current);
    }

    //FIXME ANTONIO
    /**
     * Remove an event from a calendar
     *
     * @param Id <b>Identifier</b> of the event do delete
     * @param email <b>Identifier</b> of th user associated tho the calendar
     * from which the event will be removed
     */
    public void deleteEvent(int Id, String email) {
        Calendar current = this.getCalendar(email);
        current.removeEvent(Id);
        em.merge(current);
    }

    //FIXME ANTONIO
    /**
     *
     * @param user
     * @param event
     * @return A set of events in the <i>user</i>'s {@link calendar} that
     * overlap with the <i>event</i> in parameter
     */
    public ArrayList<Event> checkOverlapping(User user, Event event) {
        Calendar calendarL = getCalendar(user.getEmail());

        List<Event> events = (List<Event>) calendarL.getEventCollection();
        ArrayList<Event> coincidentEvents = new ArrayList<>();

        for (Event event1 : events) {
            if ((event.getEndingTime().after(event1.getStartingDate()) && event.getEndingTime().before(event1.getEndingTime()))
                    || (event.getStartingDate().after(event1.getStartingDate()) && event.getStartingDate().before(event1.getEndingTime()))
                    || (event.getStartingDate().before(event1.getStartingDate()) && event.getEndingTime().after(event1.getEndingTime()) /*control if it's necessery*/)
                    || (event.getStartingDate().after(event1.getStartingDate()) && event.getEndingTime().before(event1.getEndingTime()) /*control if it's necessery*/)
                    || (event.getStartingDate().equals(event1.getStartingDate()))
                    || (event.getStartingDate().equals(event1.getEndingTime()))
                    || (event.getEndingTime().equals(event1.getStartingDate()))
                    || (event.getEndingTime().equals(event1.getEndingTime()))) {

                if (!Objects.equals(event.getId(), event1.getId())) {
                    coincidentEvents.add(event1);
                }
            }
        }
        return coincidentEvents;
    }

    /**
     * This method set the visibility of the current {@link User}'s
     * {@link Calendar}
     *
     * @param visibility {@link Boolean} value: true if the {@link Calendar} is
     * 'Public', false if the {@link Calendar} is 'Private'.
     */
    public void setVisibility(Boolean visibility) {

        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();

        Principal principal = request.getUserPrincipal();

        Calendar current = this.getCalendar(principal.getName());

        current.setVisibility(visibility);

        em.merge(current);
    }

    //Per esportare serve Calendar(email,default,visibility)+Eventi(Lista)
    //Evento(id, name, location, startingDate, endingDate, visibility, description, outdoor, creator(?),invited)
    /**
     * This method trasforms the current {@link User}'s {@link Calendar} in a
     * iCalendar (.ics) to allow the exportation from this application.
     *
     * @return {@link StreamedContent} representing the current user's calendar
     * im iCalendar format
     * @throws ValidationException
     * @throws ParserException
     */
    public StreamedContent exportCalendar() throws ValidationException, ParserException {

        //Creates a calendar in a format valid to exportation (iCalendar)
        net.fortuna.ical4j.model.Calendar icsCalendar = new net.fortuna.ical4j.model.Calendar();
        //Principal proprieties setting
        icsCalendar.getProperties().add(new ProdId("-//ALJFCE Inc//Meteocal 1.0//EN"));
        icsCalendar.getProperties().add(Version.VERSION_2_0);
        icsCalendar.getProperties().add(CalScale.GREGORIAN);

        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();

        Principal principal = request.getUserPrincipal();

        //The current user's calendar
        Calendar current = this.getCalendar(principal.getName());

        //Cycle on events in calendar to add them on calendar to export
        for (Event e : current.getEventCollection()) {

            Integer id = e.getId();
            String title = e.getTitle();
            String location = e.getLocation();
            Date startingDate = e.getStartingDate();
            Date endingDate = e.getEndingTime();
            String description = e.getDescription();
            Boolean outdoor = e.getOutdoor();
            Boolean visibility = e.getVisibility();

            //formato evento per export
            VEvent ve = new VEvent();

            //conversione startingDate
            GregorianCalendar convertedStartingDate = new GregorianCalendar();
            convertedStartingDate.set(startingDate.getYear() + 1900, startingDate.getMonth(), startingDate.getDate(),
                    startingDate.getHours(), startingDate.getMinutes(), startingDate.getSeconds());
            DateTime startDt = new DateTime(convertedStartingDate.getTime());
            //conversione endingDate
            GregorianCalendar convertedEndingingDate = new GregorianCalendar();
            convertedEndingingDate.set(endingDate.getYear() + 1900, endingDate.getMonth(), endingDate.getDate(),
                    endingDate.getHours(), endingDate.getMinutes(), endingDate.getSeconds());
            DateTime endDt = new DateTime(convertedEndingingDate.getTime());

            //VEvent's proprieties definition
            Uid uid = new Uid(id.toString());
            Summary summary = new Summary(title);
            Location vlocation = new Location(location);
            DtStart dtstart = new DtStart(startDt);
            DtEnd dtend = new DtEnd(endDt);
            Description vdescription = new Description(description);
            LocationType voutdoor;
            if (outdoor) {
                voutdoor = new LocationType("Outdoor");
            } else {
                voutdoor = new LocationType("Indoor");
            }
            Clazz clazz;
            if (visibility) {
                clazz = new Clazz("Public");

            } else {
                clazz = new Clazz("Private");
            }

            //Add proprieties to VEvent
            ve.getProperties().add(uid);
            ve.getProperties().add(summary);
            ve.getProperties().add(vlocation);
            ve.getProperties().add(dtstart);
            ve.getProperties().add(dtend);
            ve.getProperties().add(vdescription);
            ve.getProperties().add(voutdoor);
            ve.getProperties().add(clazz);

            // Add the event to caledar to export
            icsCalendar.getComponents().add(ve);

        }//fine ciclo

        InputStream stream = new ByteArrayInputStream(icsCalendar.toString().getBytes(StandardCharsets.UTF_8));
        User user = em.find(User.class, principal.getName());
        String calendarName = "calendar-" + user.getName() + "_" + user.getSurname() + ".ics";

        return new DefaultStreamedContent(stream, "calendar/ics", calendarName);

    }

    /**
     * This method allows the importation of an iCalendar file with (.ics)
     * extention into the application, all calendar's events are imported and
     * saved in the persistence context. There may be time overlaps with events
     * already in the user's calendar.
     *
     * @param ufile {@link UploadedFile} representing the imported iCalendar to
     * save
     * @throws IOException
     * @throws ParserException
     */
    public void importCalendar(UploadedFile ufile) throws IOException, ParserException {

        //Check that the file is not null
        //in it is returned and show a warning to the user
        if (ufile == null) {
            FacesContext.getCurrentInstance().addMessage(
                    null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR,
                            "File error",
                            "Unable to upload file"));
            return;

        }

        InputStream myCalendarInputStream = ufile.getInputstream();
        CalendarBuilder builder = new CalendarBuilder();
        net.fortuna.ical4j.model.Calendar icsCalendar = null;

        try {
            icsCalendar = builder.build(myCalendarInputStream);
        } catch (IOException | ParserException e) {

            //Added to catch an invalid file
            FacesContext.getCurrentInstance().addMessage(
                    null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR,
                            "Invalid File",
                            "Invalid ics file"));
            return;

        }
        FacesContext context = FacesContext.getCurrentInstance();
        HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();

        Principal principal = request.getUserPrincipal();

        //the user's calendar
        Calendar current = this.getCalendar(principal.getName());

        ArrayList<VEvent> arrayVEvents = icsCalendar.getComponents();

        for (VEvent ve : arrayVEvents) {

            Summary summary = ve.getSummary();
            Location vlocation = ve.getLocation();
            DtStart dtstart = ve.getStartDate();
            DtEnd dtend = ve.getEndDate();
            Description vdescription = ve.getDescription();
            LocationType voutdoor = (LocationType) ve.getProperties().getProperty(Property.LOCATION_TYPE);
            if (voutdoor == null) {
                voutdoor = new LocationType("Indoor");
            }
            Clazz clazz = ve.getClassification();

            String title = summary.getValue();
            String location = vlocation.getValue();
            Date startingDate = dtstart.getDate();
            Date endingDate = dtend.getDate();
            String description = vdescription.getValue();

            WeatherInformation wf = null;

            Boolean outdoor;
            if (voutdoor.getValue().equals("Outdoor")) {
                outdoor = true;
                try {
                    wf = fm.getForecast(location, startingDate);
                } catch (DayOutOfRangeException | IllegalArgumentException | ForecastNotFoundException ex) {
                    Logger.getLogger(CalendarManager.class.getName()).log(Level.SEVERE, null, ex);
                }
            } else {
                outdoor = false;
            }
            Boolean visibility;
            if (clazz.getValue().equals("Public")) {
                visibility = true;
            } else {
                visibility = false;
            }

            Event e = new Event();

            e.setTitle(title);
            e.setLocation(location);
            e.setStartingDate(startingDate);
            e.setEndingTime(endingDate);
            e.setDescription(description);
            e.setOutdoor(outdoor);
            e.setVisibility(visibility);
            if (wf != null) {
                ArrayList<WeatherInformation> arrayWf = new ArrayList<WeatherInformation>();
                arrayWf.add(wf);
                e.setWeatherInformationCollection(arrayWf);
            }

            try {
                e.setCreator(pm.getProfile(principal.getName()));
            } catch (UserNotFoundException ex) {
                Logger.getLogger(CalendarManager.class.getName()).log(Level.SEVERE, null, ex);
            }

            em.persist(e);

            current.addEvent(e);
        }

        em.persist(current);

        FacesContext.getCurrentInstance().addMessage(
                null,
                new FacesMessage(FacesMessage.SEVERITY_INFO,
                        "Calendar Imported",
                        "Calendar sucessfully imported\n"));
    }

    //TODO ANTONIO is this usefull?
    public List<Event> getEvents() {
        return (List<Event>) calendar.getEventCollection();
    }

    //TODO ANTONIO is this usefull?
    public void addEvents(List<Event> events) {
        Calendar eCalendar = new Calendar();
        eCalendar.setEventCollection(events);//TO see again!!!!!!!!!!!!!!!!
        for (int i = 0; i < events.size(); i++) {
            em.persist(events.get(i));
        }
        em.persist(eCalendar);

    }

    public ArrayList loadEvents(User User) {
        return null;
    }

}
